﻿namespace Contoso.ActiveDirectory {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.DirectoryServices;
    using System.DirectoryServices.AccountManagement;
    using System.DirectoryServices.ActiveDirectory;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.AccessControl;
    using System.Security.Principal;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    #endregion

    public class UserAccountMethods : UserComputerMethods {

        internal const int LOGON32_LOGON_INTERACTIVE = 2;
        internal const int LOGON32_PROVIDER_DEFAULT = 0;

        /// <summary>
        /// Adds an access rule to the specified target user account to grant the account unlock permission specified security principal
        /// </summary>
        /// <param name="targetAccountDN">The target account DN</param>
        /// <param name="securityPrincipal">The security principal that will be granted the permission</param>
        /// <returns>True if successful</returns>
        /// <remarks>Grants write access to the lockoutTime attribute</remarks>
        public bool AddAccountUnlockAccessRule(string targetAccountDN, IdentityReference securityPrincipal) {
            if (string.IsNullOrWhiteSpace(targetAccountDN)) {
                throw new ArgumentNullException("targetAccountDN");
            }
            if (securityPrincipal == null) {
                throw new ArgumentNullException("securityPrincipal");
            }

            var unlockAccountAccessRule = CreateAccountUnlockAccessRule(securityPrincipal);
            return ADDataAccess.AddAccessRule(targetAccountDN, unlockAccountAccessRule);
        }

        public bool AuthenticateUser(string domainNetBiosName, string samAccountName, SecureString password) {
            Debug.WriteLine(string.Format(@"{0}  [ThreadID: {1}]  {2} for domain\samAccountName: {3}\{4}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), string.IsNullOrWhiteSpace(domainNetBiosName) ? "NULL" : domainNetBiosName, string.IsNullOrWhiteSpace(samAccountName) ? "NULL" : samAccountName));

            #region Validation
            if (string.IsNullOrWhiteSpace(domainNetBiosName) || string.IsNullOrWhiteSpace(samAccountName) || (password == null)) {
                throw new ApplicationException(string.Format("Parameter(s) not specified.  domainNetBiosName: {0} user: {1} password: {2}",
                    domainNetBiosName != null ? domainNetBiosName : "NULL", samAccountName != null ? samAccountName : "NULL", password != null ? password.ToString() : "NULL"));
            }
            #endregion

            var methodParameters = new MethodParameters() {
                ServerNameOrAdjustedBindPath = null,
                DomainNetbiosName = domainNetBiosName.Trim(),
                SamAccountName = samAccountName.Trim(),
                Password = password
            };

            return ConnectionManager.DirectoryServerHandler<MethodParameters, bool>(
                    ref methodParameters,
                    (x) => {
                        bool isAuthenticated = false;
                        var availableDC = ConnectionManager.SelectDC(methodParameters.DomainNetbiosName);
                        methodParameters.ServerNameOrAdjustedBindPath = availableDC.DNSName;

                        using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, availableDC.DNSName)) {
                            isAuthenticated = pc.ValidateCredentials(methodParameters.SamAccountName, methodParameters.Password.ReadString(), ContextOptions.Sealing | ContextOptions.Signing | ContextOptions.ServerBind);
                        }

                        return isAuthenticated;
                    });
        }

        private ActiveDirectoryAccessRule CreateAccountUnlockAccessRule(IdentityReference securityPrincipal) {
            if (securityPrincipal == null) {
                throw new ArgumentNullException("securityPrincipal");
            }

            var lockoutTimeAttribute = ADDataAccess.ADSchemaAttributes["lockoutTime"];
            var unlockAccountAccessRule = new ActiveDirectoryAccessRule(
                securityPrincipal, 
                ActiveDirectoryRights.ReadProperty | ActiveDirectoryRights.WriteProperty,
                AccessControlType.Allow,
                lockoutTimeAttribute.SchemaIdGuid, 
                ActiveDirectorySecurityInheritance.None);

            return unlockAccountAccessRule;
        }

        /// <summary>
        /// Creates a user account and returns the distinguished name of the created account
        /// </summary>
        /// <param name="ouDN">The OU where the account will be created.</param>
        /// <param name="samAccountName">The name to be used for the samAccountName and CN</param>
        /// <returns>Distinguished Name of the account</returns>
        /// <remarks>
        /// Username is converted to lowercase.
        /// Password policy may prevent creating new accounts, and result in the exception:
        /// Exception: DirectoryServicesCOMException Error message: The object already exists.
        /// The account will need to have the password set and the account enabled (set UAC to Normal) before it will be usable.
        /// </remarks>
        public string CreateUserAccount(string ouDN, string samAccountName) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} OU Path: {3} Account Name: {4}",
                DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(),
                string.IsNullOrWhiteSpace(ouDN) ? "NULL" : ouDN, string.IsNullOrWhiteSpace(samAccountName) ? "NULL" : samAccountName));

            #region Validation
            if (string.IsNullOrWhiteSpace(ouDN) || string.IsNullOrWhiteSpace(samAccountName)) {
                throw new ApplicationException(string.Format("Parameter(s) not specified.  ouPath: {0} accountName: {1}",
                    ouDN != null ? ouDN : "NULL", samAccountName != null ? samAccountName : "NULL"));
            }

            if (!IsUserSamAccountNameValid(samAccountName)) {
                throw new InvalidOperationException(string.Format("samAccountName invalid: {0}", samAccountName));
            }
            #endregion

            var methodParameters = new MethodParameters() {
                ServerNameOrAdjustedBindPath = null,
                OUDN = ouDN.Trim(),
                SamAccountName = samAccountName.Trim().ToLower()
            };

            methodParameters.CreatedObjectDN = createUserAccount(methodParameters);

            if (string.IsNullOrWhiteSpace(methodParameters.CreatedObjectDN)) {
                throw new ApplicationException("User DN not returned when creating account");
            }

            return methodParameters.CreatedObjectDN;
        }

        private string createUserAccount(MethodParameters methodParameters) {
            return ConnectionManager.DirectoryServerHandler<MethodParameters, string>(
                ref methodParameters,
                (x) => {
                    methodParameters.ServerNameOrAdjustedBindPath = ADDataAccess.GetAdjustedBindPath(forupdate: true, bindPath: methodParameters.OUDN);

                    #region Validation
                    if (!ADDataAccess.ObjectExists(methodParameters.ServerNameOrAdjustedBindPath)) {
                        throw new ApplicationException(string.Format("Path does not exist: {0}", methodParameters.ServerNameOrAdjustedBindPath));
                    }
                    #endregion

                    using (var ouDirectoryEntry = new DirectoryEntry(methodParameters.ServerNameOrAdjustedBindPath, null, null, methodParameters.AuthenticationTypes))
                    using (var userDirectoryEntry = ouDirectoryEntry.Children.Add("CN=" + methodParameters.SamAccountName, "user")) {

                        int uacValue = (int)(UserAccountControlFlags.AccountDisabled | UserAccountControlFlags.PasswordNotRequired);
                        try {
                            userDirectoryEntry.Properties["sAMAccountName"].Value = methodParameters.SamAccountName;
                            userDirectoryEntry.Properties["userAccountControl"].Value = uacValue;
                        }
                        catch (Exception e) {
                            Debug.WriteLine(e.VerboseExceptionString());
                        }

                        userDirectoryEntry.CommitChanges();
                        methodParameters.CreatedObjectDN = ADDataAccess.GetDNFromLdapDN(userDirectoryEntry.Path);
                    }

                    return methodParameters.CreatedObjectDN;
                });
        }

        /// <summary>
        /// Disables the specified account
        /// </summary>
        /// <param name="dn">The distinguishedName of the account</param>
        /// <returns>True if successful</returns>
        public bool DisableAccount(string dn) {
            if (string.IsNullOrWhiteSpace(dn)) {
                throw new ArgumentNullException("dn");
            }
            var account = ADDataAccess.GetADObjectProperties<UserAccount>(dn, null, string.Format("distinguishedName={0}", dn), PartitionScope.LocalPartition).SingleOrDefault();
            if (account == null) {
                throw new ApplicationException(string.Format("Unable to locate User account for DN: {0}", dn));
            }
            return this.DisableAccount(account);
        }

        /// <summary>
        /// Disables the specified account
        /// </summary>
        /// <param name="account">The UserProperties object for the account</param>
        /// <returns>True if successful</returns>
        public bool DisableAccount(UserAccount account) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), ((account != null) && !string.IsNullOrWhiteSpace(account.DN)) ? account.DN : "NA"));

            return base.DisableAccount(account);
        }

        /// <summary>
        /// Enables the specified account
        /// </summary>
        /// <param name="dn">The distinguishedName of the account</param>
        /// <returns>True if successful</returns>
        public bool EnableAccount(string dn) {
            if (string.IsNullOrWhiteSpace(dn)) {
                throw new ArgumentNullException("dn");
            }
            var account = ADDataAccess.GetADObjectProperties<UserAccount>(dn, null, string.Format("distinguishedName={0}", dn), PartitionScope.LocalPartition).SingleOrDefault();
            if (account == null) {
                throw new ApplicationException(string.Format("Unable to locate User account for DN: {0}", dn));
            }
            return this.EnableAccount(account);
        }

        /// <summary>
        /// Enables the specified account
        /// </summary>
        /// <param name="account">The UserProperties object for the account</param>
        /// <returns>True if successful</returns>
        public bool EnableAccount(UserAccount account) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), ((account != null) && !string.IsNullOrWhiteSpace(account.DN)) ? account.DN : "NA"));

            return base.EnableAccount(account);
        }

        /// <summary>
        /// Gets a UserPrincipal object for the specified Domain and SamAccountName
        /// </summary>
        /// <param name="domain">The Domain</param>
        /// <param name="samAccountName">The SamAccountName</param>
        /// <returns>The UserPrincipal</returns>
        public UserPrincipal GetUserPrincipal(string domain, string samAccountName) {
            Debug.WriteLine(string.Format(@"{0}  [ThreadID: {1}]  {2} for domain\samAccountName: {3}\{4}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), string.IsNullOrWhiteSpace(domain) ? "NULL" : domain, string.IsNullOrWhiteSpace(samAccountName) ? "NULL" : samAccountName));

            #region Validation
            if (string.IsNullOrWhiteSpace(domain) || string.IsNullOrWhiteSpace(samAccountName)) {
                throw new ApplicationException(string.Format("Parameter(s) not specified.  domainNetBiosName: {0} samAccountName: {1}",
                    domain != null ? domain : "NULL", samAccountName != null ? samAccountName : "NULL"));
            }

            DomainConfiguration domainInfo = ADDataAccess.ADDomainInfo.Where(x => string.Equals(x.DistinguishedName, domain, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if (domainInfo == null) domainInfo = ADDataAccess.ADDomainInfo.Where(x => string.Equals(x.NetbiosName, domain, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if (domainInfo == null) {
                throw new ApplicationException(string.Format("Domain not found: {0}", domain));
            }
            #endregion

            var methodParameters = new MethodParameters() {
                ServerNameOrAdjustedBindPath = null,
                DomainNetbiosName = domainInfo.NetbiosName,
                SamAccountName = samAccountName
            };

            return ConnectionManager.DirectoryServerHandler<MethodParameters, UserPrincipal>(
                    ref methodParameters,
                    (x) => {
                        UserPrincipal userPrincipal = null;
                        var availableDC = ConnectionManager.SelectDC(methodParameters.DomainNetbiosName);
                        methodParameters.ServerNameOrAdjustedBindPath = availableDC.DNSName;

                        using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, availableDC.DNSName)) {
                            userPrincipal = UserPrincipal.FindByIdentity(pc, methodParameters.SamAccountName);
                        }
                        return userPrincipal;
                    });
        }

        /// <summary>
        /// Determines if the target account access rules has the specific access rule for account unlock for the specified security principal
        /// </summary>
        /// <param name="targetAccountAccessRules">The target account's access rules</param>
        /// <param name="securityPrincipal">The security principal to check for the access rule</param>
        /// <returns>True if successful</returns>
        public bool HasAccountUnlockAccessRule(AuthorizationRuleCollection targetAccountAccessRules, IdentityReference securityPrincipal) {
            if (targetAccountAccessRules == null) {
                throw new ArgumentNullException("targetAccountAccessRules");
            }
            if (securityPrincipal == null) {
                throw new ArgumentNullException("securityPrincipal");
            }

            var unlockAccountAccessRule = CreateAccountUnlockAccessRule(securityPrincipal);
            return ADDataAccess.HasAccessRule(targetAccountAccessRules, unlockAccountAccessRule);            
        }

        /// <summary>
        /// Impersonate a user
        /// </summary>
        /// <param name="userName">The name of the user</param>
        /// <param name="domain">The Active Directory domain name or local machine name of the user account</param>
        /// <param name="password">The password of the user</param>
        /// <param name="impersonationLevel">Delegation, Impersonation, Identification, or Anonymous</param>
        /// <remarks>Enclose in a using statement or dispose of the WindowsIdentity when finished</remarks>
        public WindowsIdentity ImpersonateUser(SecureString userName, string domain, SecureString password) {
            return this.ImpersonateUser(userName, domain, password, SecurityImpersonationLevel.Delegation);
        }

        public WindowsIdentity ImpersonateUser(SecureString userName, string domain, SecureString password, SecurityImpersonationLevel impersonationLevel) {
            WindowsIdentity windowsIdentity = null;
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;

            try {
                if (RevertToSelf()) {
                    if (LogonUser(userName.ReadString(), domain, password.ReadString(), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0) {
                        if (DuplicateToken(token, (int)impersonationLevel, ref tokenDuplicate) != 0) {
                            windowsIdentity = new WindowsIdentity(tokenDuplicate);
                        }
                        else {
                            throw new Win32Exception(Marshal.GetLastWin32Error());
                        }
                    }
                    else {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
                else {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
            finally {
                if (token != IntPtr.Zero) {
                    CloseHandle(token);
                }
                if (tokenDuplicate != IntPtr.Zero) {
                    CloseHandle(tokenDuplicate);
                }
            }

            return windowsIdentity;
        }

        /// <summary>
        /// Invalid characters: \/[]:;|=,+*?<>"
        /// 20 character maximum
        /// Cannot start or end with space or end with . (dot, period)
        /// </summary>
        public bool IsUserSamAccountNameValid(string samAccountName) {
            bool isValid = (samAccountName.Length > 0)
                && (samAccountName.Length <= 20)
                && !samAccountName.EndsWith(".")
                && !samAccountName.StartsWith(" ")
                && !samAccountName.EndsWith(" ");

            if (isValid) {
                isValid = Regex.IsMatch(samAccountName, ADDataAccess.SamAccountNameDoesNotContainInvalidCharactersFilter);
            }

            return isValid;
        }

        /// <summary>
        /// Remove PasswordNeverExpires flag from an account
        /// </summary>
        /// <param name="dn">Distinguished Name of object on which to perform action</param>
        /// <returns>True if successful</returns>
        /// <remarks>
        public bool RemovePasswordNeverExpires(string dn) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), string.IsNullOrWhiteSpace(dn) ? "NULL" : dn));

            if (string.IsNullOrWhiteSpace(dn)) {
                throw new ArgumentNullException("dn");
            }
            var account = ADDataAccess.GetADObjectProperties<UserAccount>(dn, null, string.Format("distinguishedName={0}", dn), PartitionScope.LocalPartition).SingleOrDefault();
            if (account == null) {
                throw new ApplicationException(string.Format("Unable to locate User account for DN: {0}", dn));
            }
            return this.RemovePasswordNeverExpires(account);
        }
        public bool RemovePasswordNeverExpires(UserAccount account) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), ((account != null) && !string.IsNullOrWhiteSpace(account.DN)) ? account.DN : "NA" ));

            if (account == null) {
                throw new ArgumentNullException("account");
            }
            if (string.IsNullOrWhiteSpace(account.DN)) {
                throw new ArgumentNullException("account.DN");
            }
            return this.RemoveUserAccountControlFlag(account, UserAccountControlFlags.PasswordNeverExpires);
        }

        /// <summary>
        /// Remove PasswordNotRequired flag from an account
        /// </summary>
        /// <param name="dn">Distinguished Name of object on which to perform action</param>
        /// <returns>True if successful</returns>
        /// <remarks>
        public bool RemovePasswordNotRequired(string dn) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), string.IsNullOrWhiteSpace(dn) ? "NULL" : dn));

            if (string.IsNullOrWhiteSpace(dn)) {
                throw new ArgumentNullException("dn");
            }
            var account = ADDataAccess.GetADObjectProperties<UserAccount>(dn, null, string.Format("distinguishedName={0}", dn), PartitionScope.LocalPartition).SingleOrDefault();
            if (account == null) {
                throw new ApplicationException(string.Format("Unable to locate User account for DN: {0}", dn));
            }

            return this.RemovePasswordNotRequired(account);
        }
        public bool RemovePasswordNotRequired(UserAccount account) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), ((account != null) && !string.IsNullOrWhiteSpace(account.DN)) ? account.DN : "NA"));

            if (account == null) {
                throw new ArgumentNullException("account");
            }
            if (string.IsNullOrWhiteSpace(account.DN)) {
                throw new ArgumentNullException("account.DN");
            }
            return this.RemoveUserAccountControlFlag(account, UserAccountControlFlags.PasswordNotRequired);
        }

        /// <summary>
        /// Uses DirectoryEntry.Invoke("SetPassword")
        /// </summary>
        /// <param name="userDN"></param>
        /// <param name="password"></param>
        /// <remarks>
        /// May result in the following exception. System.Runtime.InteropServices.COMException: Member not found. 
        /// (Exception from HRESULT: 0x80020003 (DISP_E_MEMBERNOTFOUND))
        /// 
        /// May result in exception if the password is not set or if the password does not conform to password policy:
        /// System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> 
        /// System.DirectoryServices.DirectoryServicesCOMException: The server is unwilling to process the request. 
        /// (Exception from HRESULT: 0x80072035)
        /// </remarks>
        public bool SetPassword(string userDN, SecureString password) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} User DN: {3}",
                DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), string.IsNullOrWhiteSpace(userDN) ? "NULL" : userDN));

            #region Validation
            if (string.IsNullOrWhiteSpace(userDN)) {
                throw new ArgumentNullException("userDN");
            }
            if (password == null) {
                throw new ArgumentNullException("password");
            }
            #endregion

            var methodParameters = new MethodParameters() {
                ServerNameOrAdjustedBindPath = null,
                ObjectDN = userDN.Trim(),
                Password = password
            };

            return ConnectionManager.DirectoryServerHandler<MethodParameters, bool>(
                ref methodParameters,
                (x) => {
                    methodParameters.ServerNameOrAdjustedBindPath = ADDataAccess.GetAdjustedBindPath(forupdate: true, bindPath: methodParameters.ObjectDN);
                    methodParameters.DomainNetbiosName = ADDataAccess.GetDomainNetBiosNameFromDNOrDNSName(methodParameters.ObjectDN);

                    #region Validation
                    if (!ADDataAccess.ObjectExists(methodParameters.ObjectDN)) {
                        throw new ApplicationException(string.Format("Path does not exist: {0}", methodParameters.ServerNameOrAdjustedBindPath));
                    }
                    if (methodParameters.Password == null) {
                        throw new ArgumentNullException("methodParameters.Password");
                    }
                    if (methodParameters.Password.ReadString() == null) {
                        throw new ArgumentNullException("methodParameters.Password.ReadString()");
                    }
                    if (string.IsNullOrWhiteSpace(methodParameters.Password.ReadString())) {
                        throw new ArgumentOutOfRangeException("methodParameters.Password.ReadString() is empty.");
                    }
                    #endregion

                    var availableDC = ADDataAccess.GetServerFromLdapDN(methodParameters.ServerNameOrAdjustedBindPath);
                    using (var pc = new PrincipalContext(ContextType.Domain, availableDC))
                    using (var userPrincipal = UserPrincipal.FindByIdentity(pc, methodParameters.ObjectDN)) {

                        ContextOptions contextOptions = ContextOptions.Sealing | ContextOptions.Signing;
                        var authenticationTypes = ADDataAccess.GetAdjustedAuthenticationType(methodParameters.ServerNameOrAdjustedBindPath);
                        if (authenticationTypes.HasFlag(AuthenticationTypes.ServerBind)) {
                            contextOptions |= ContextOptions.ServerBind;
                        }

                        userPrincipal.SetPassword(methodParameters.Password.ReadString());
                        return true;
                    }
                });
        }

        /// <summary>
        /// Marks an account for "User must change password at next logon" by setting pwdLastSet to zero
        /// </summary>
        /// <param name="userDN">The DN of the user object</param>
        /// <returns>True if successful</returns>
        public bool SetPasswordExpired(string userDN) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} User DN: {3}",
                DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), string.IsNullOrWhiteSpace(userDN) ? "NULL" : userDN));

            #region Validation
            if (string.IsNullOrWhiteSpace(userDN)) {
                throw new ArgumentNullException("userDN");
            }
            #endregion

            var methodParameters = new MethodParameters() {
                ServerNameOrAdjustedBindPath = null,
                ObjectDN = userDN.Trim(),
            };

            return ConnectionManager.DirectoryServerHandler<MethodParameters, bool>(
                ref methodParameters,
                (x) => {
                    methodParameters.ServerNameOrAdjustedBindPath = ADDataAccess.GetAdjustedBindPath(forupdate: true, bindPath: methodParameters.ObjectDN);
                    methodParameters.DomainNetbiosName = ADDataAccess.GetDomainNetBiosNameFromDNOrDNSName(methodParameters.ObjectDN);

                    #region Validation
                    if (!ADDataAccess.ObjectExists(methodParameters.ObjectDN)) {
                        throw new ApplicationException(string.Format("Path does not exist: {0}", methodParameters.ServerNameOrAdjustedBindPath));
                    }
                    #endregion

                    var availableDC = ADDataAccess.GetServerFromLdapDN(methodParameters.ServerNameOrAdjustedBindPath);
                    using (var pc = new PrincipalContext(ContextType.Domain, availableDC))
                    using (var userPrincipal = UserPrincipal.FindByIdentity(pc, methodParameters.ObjectDN)) {

                        ContextOptions contextOptions = ContextOptions.Sealing | ContextOptions.Signing;
                        var authenticationTypes = ADDataAccess.GetAdjustedAuthenticationType(methodParameters.ServerNameOrAdjustedBindPath);
                        if (authenticationTypes.HasFlag(AuthenticationTypes.ServerBind)) {
                            contextOptions |= ContextOptions.ServerBind;
                        }

                        userPrincipal.ExpirePasswordNow();
                        return true;
                    }
                });
        }

        public SecureString SetRandomPassword(string userDN) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} User DN: {3}",
                DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), string.IsNullOrWhiteSpace(userDN) ? "NULL" : userDN));

            var securePassword = RandomPasswordGenerator.GeneratePassword();
            if (this.SetPassword(userDN, securePassword)) {
                securePassword = null;
            }

            return securePassword;
        }

        /// <summary>
        /// Unlocks a machine or user account
        /// </summary>
        /// <param name="dn">Distinguished Name of object to unlock</param>
        /// <returns>True if successful</returns>
        public bool UnlockAccount(string dn) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), (dn ?? "NULL")));

            if (string.IsNullOrWhiteSpace(dn)) {
                throw new ArgumentNullException("dn");
            }
            var account = ADDataAccess.GetADObjectProperties<UserAccount>(dn, null, string.Format("distinguishedName={0}", dn), PartitionScope.LocalPartition).SingleOrDefault();
            if (account == null) {
                throw new ApplicationException(string.Format("Unable to locate User account for DN: {0}", dn));
            }

            return this.UnlockAccount(account);
        }
        public bool UnlockAccount(UserAccount account) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), ((account != null) && !string.IsNullOrWhiteSpace(account.DN)) ? account.DN : "NA"));

            if (account == null) {
                throw new ArgumentNullException("account");
            }
            if (string.IsNullOrWhiteSpace(account.DN)) {
                throw new ArgumentNullException("account.DN");
            }
            return this.RemoveUserAccountControlFlag(account, UserAccountControlFlags.AccountLockedOut);
        }

        #region Unmanaged code
        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern int LogonUser(
            string lpszUserName,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern int DuplicateToken(
            IntPtr hToken,
            int impersonationLevel,
            ref IntPtr hNewToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool RevertToSelf();

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern bool CloseHandle(IntPtr handle);
        #endregion

    }
}
